|     junit tools java test   |    

[被测试类 - 测试用例 - Runner] 的三角构架

一个最基本的JUnit测试需要三样东西:

  1. 被测试类: 需要被测试的目标代码。
  2. 测试用例: 用来封装测试代码的类。
  3. Runner: 执行测试的入口。

下面是一个最简单的例子,

被测试类

MessageUtil.java可以打印用户传递给它的消息。下面的代码中包含了一条bug。系统希望printMessage()直接打印出给入的原始信息。但这里程序员不小心多打了个Hi.

/**
 *  文件路径:/Users/Wei/JavaCode/src/com/ciaoshen/junit/MessageUtil.java
 */
package com.ciaoshen.junit;

public class MessageUtil {
   private String message;

   public MessageUtil(String message){
      this.message = "Hi " + message; // Bug! 原先希望直接打印出message。
   }
   public String printMessage(){
      System.out.println(message);
      return message;
   }
}

主测试类

MessageUtilTest.java是测试的主体。这里需要导入org.junit.Test库,因为代码里用到了@Test注释,它告诉JUnit哪个方法是需要执行测试方法。在JUnit 4之前,用户需要遵守一个重要的人为的约定: 所有测试方法必须以test开头,JUnit才会运行它们。. 从JUnit 4开始,可以用 @Test 注释来标注。这是JUnit非常重要的一个特性。它实现的原理,是基于在运行时可见的注释,以及动态代理技术。这里先不展开。

另外一个org.junit.Assert.assertEquals库属于断言系统。说白了,JUnit就是用断言来判断,实际得到的结果,是否和正确结果一致。所以断言系统在JUnit里占有很重要的位置。下面的代码中,assertEquals()函数会验证它的两个参数(即目标结果和实际结果)是否一致。

/**
 *  文件路径:/Users/Wei/JavaCode/Test/com/ciaoshen/junit/MessageUtilTest.java
 */
package com.ciaoshen.junit;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class MessageUtilTest {
    private String message = "Hello Ronald";
    MessageUtil mu = new MessageUtil(message);

    @Test
    public void testPrintMessage() {
        assertEquals(message, mu.printMessage());
    }
}

Runner工具,Result工具和Failure组件

我们当然可以自己运行测试类MessageUtilTest.java。比如像这样,

/**
 *  文件路径:/Users/Wei/JavaCode/Test/com/ciaoshen/junit/MessageUtilTest.java
 */
package com.ciaoshen.junit;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class MessageUtilTest {
    private String message = "Hello Ronald";
    MessageUtil mu = new MessageUtil(message);

    @Test
    public void testPrintMessage() {
        assertEquals(message, mu.printMessage());
    }
    public static void main(String[] args) { // 自己运行测试
        MessageUtilTest test = new MessageUtilTest();
        test.testPrintMessage();
    }
}

正常的时候assertEquals()的返回值是void,所以没什么反应。一旦被测试代码出错,比如printMessage()的输出和用户的输入不一致了,assertEquals()函数会抛出异常org.junit.ComparisonFailure

MacBook-Pro-de-Wei:JavaCode Wei$ sh run.sh MessageUtil
HiHello Ronald
Exception in thread "main" org.junit.ComparisonFailure: expected:<H[]ello Ronald> but was:<H[iH]ello Ronald>
	at org.junit.Assert.assertEquals(Assert.java:115)
	at org.junit.Assert.assertEquals(Assert.java:144)
	at com.ciaoshen.junit.MessageUtilTest.testPrintMessage(MessageUtilTest.java:15)
	at com.ciaoshen.junit.MessageUtilTest.main(MessageUtilTest.java:19)

实际测试中,经常会有很多错误一起出现。我们就需要自己捕获,并收集异常,然后以比较容易阅读的方式打印出来。这些JUnit都可以帮我们做。

JUnitCore.runClasses()函数帮我们运行主测试类,把测试结果收集到一个Result类实例中。可以用Result#getFailures()函数解析出异常并打印。

下面代码中的三个import库,分别为JUnitCoreResultFailure组件。

/**
 *  文件路径:/Users/Wei/JavaCode/Test/com/ciaoshen/junit/MessageUtilTestRunner.java
 */
package com.ciaoshen.junit;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class MessageUtilTestRunner {
    public static void main(String[] args) {
        //JUnitCore.runClasses()函数运行主测试类
        Result result = JUnitCore.runClasses(MessageUtilTest.class); //把测试结果收集到一个`Result`类实例中
        for (Failure failure : result.getFailures()) { // getFailures()函数从Result实例中解析出异常
            System.out.println(failure.toString());
        }
        System.out.println(result.wasSuccessful());
    }
}

Demo根目录下的文件结构如下,

把被测试代码和测试类放在同一个包,但分放在不同的文件夹下,是一个良好的实践。

.
├── bin     // class文件
│   └── com
│       └── ciaoshen
│           └── leetcode
│               ├── MessageUtil.class
│               ├── MessageUtilTest.class
│               └── MessageUtilTestRunner.class // class文件可以放在一起
├── lib     // JUnit库包
│   ├── hamcrest-core-1.3.jar
│   └── junit-4.12.jar
├── src     // 我的正常代码存放位置
│   └── com
│       └── ciaoshen
│           └── leetcode
│               └── MessageUtil.java // 被测试代码
└── test    // 测试代码和业务代码在同一个包,但被放在两个不同的文件夹下。
    └── com
        └── ciaoshen
            └── leetcode
                ├── MessageUtilTest.java  // 单元测试代码
                └── MessageUtilTestRunner.java  // 运行单元测试代码

下面是编译用的bash文件。这样,第一个JUnit的程序就可以跑起来了。

# Base Path
BASE_DIR="/Users/Wei/JavaCode"
CLASS_PATH="$BASE_DIR/bin"
SOURCE_PATH="$BASE_DIR/src"
LIB_PATH="$BASE_DIR/lib"
TEST_PATH="$BASE_DIR/test"

# JUnit Library
JUNIT="$LIB_PATH/junit-4.12.jar"
HAMCREST="$LIB_PATH/hamcrest-core-1.3.jar"

### JUnit sub dir
JUNIT_PACK="com/ciaoshen/junit"
JUNIT_SRC="$SOURCE_PATH/$JUNIT_PACK"
JUNIT_TEST="$TEST_PATH/$JUNIT_PACK"

##################################
#   Compile & Run JUnit Demo
##################################
javac -cp $CLASS_PATH:$JUNIT:$HAMCREST -d $CLASS_PATH $JUNIT_SRC/$1.java $JUNIT_TEST/$1Test.java $JUNIT_TEST/$1TestRunner.java
java -cp $CLASS_PATH:$JUNIT:$HAMCREST com.ciaoshen.junit.$1TestRunner

下面是运行结果,Runner把结果显示地很有条理。

MacBook-Pro-de-Wei:JavaCode Wei$ sh run.sh MessageUtil
HiHello Ronald
testPrintMessage(com.ciaoshen.junit.MessageUtilTest): expected:<H[]ello Ronald> but was:<H[iH]ello Ronald>
false